package com.andox.fn.inf.rpc.rest.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

import com.alibaba.fastjson.JSON;
import com.andox.fn.inf.rpc.rest.HttpRemotingService;
import com.andox.fn.inf.rpc.rest.RemotingService;
import com.andox.fn.inf.rpc.rest.RequestContext;
import com.andox.fn.inf.rpc.rest.RequestContextConstants;
import com.andox.fn.inf.rpc.rest.RpcRequest;
import com.andox.fn.inf.rpc.rest.config.ReferenceConfig;
import com.andox.fn.inf.rpc.rest.exception.ServiceRuntimeException;
import com.andox.fn.inf.rpc.rest.serializer.KryoSerializer;
import com.andox.fn.inf.rpc.rest.serializer.Serializer;


/**   
 * @ClassName:   ClientInvocationHandler   
 * @author:      Andox
 * @date:        2019年7月30日 下午6:01:00   
 *     
 */
public class ClientInvocationHandler implements InvocationHandler{
	
	private ReferenceConfig referenceConfig;
	
	private Serializer serializer = new KryoSerializer();//FSTSerializer.DEFAULT_INSTANCE;
	
	private RemotingService remotingService = new HttpRemotingService();
	
	public ClientInvocationHandler(ReferenceConfig referenceConfig) {
		
		this.referenceConfig = referenceConfig;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if (method.getDeclaringClass().equals(Object.class)) {
			return null;
		}
		
		if (!method.getDeclaringClass().equals(this.referenceConfig.getInterfaceClass())) {
			throw new IllegalStateException();
		}
		
		return this.doInvoke(method, args);
	}

	private Object doInvoke(Method method, Object[] args, int x) {
		
		try {
			RequestContext.instance().reset();
			
			String url = referenceConfig.getBaseUrl() + "/" + this.buildUrl(method);
			
			RequestContext.instance().putProperty(RequestContextConstants.REQUEST_URL, url);
			RequestContext.instance().putProperty(RequestContextConstants.RETURN_TYPE, method.getReturnType());
			RequestContext.instance().putProperty(RequestContextConstants.TIMEOUT_IN_MILLISECONDS, referenceConfig.getTimeout());
			
			RpcRequest request = this.buildRequest(method, args, url, referenceConfig.getUniqueId());
			RequestContext.instance().putProperty(RequestContextConstants.SERIALIZER, this.serializer);
			
			if (referenceConfig.isAsync()) {
				remotingService.invokeAsync(request, referenceConfig.getTimeout(), null);
			}else if (referenceConfig.isOneway()) {
				remotingService.invokeOneway(request);
			}else {
				Object biz = remotingService.invokeSync(request, referenceConfig.getTimeout()).recreate(serializer);
				return biz;
			}
			
			if (method.getReturnType().equals(Void.TYPE)) {
				return Void.TYPE;
			}else {
				return null;
			}
		} catch (Exception e) {
			throw new ServiceRuntimeException(e);
		} finally {
			removeContext();
		}
	}
	
	private void removeContext() {
		if (!referenceConfig.isAsync()) {
			RequestContext.removeContext();
		}
	}
	
	private Object doInvoke(Method method, Object[] args) {
		return doInvoke(method, args, 0);
	}
	
	private String buildUrl(Method method) {
		StringBuilder sb = new StringBuilder(method.getDeclaringClass().getName());
		sb.append("/").append(method.getName());
		for (Parameter p : method.getParameters()) {
			sb.append("/").append(p.getType().getName());
		}
		
		return sb.toString();
	}
	
	private RpcRequest buildRequest(Method method, Object[] args, String url, String uniqueId) {
		RpcRequest ar = RpcRequest.newInstanceWithId();
		ar.setUniqueId(uniqueId);
		ar.setMethod(method.getName());
		Parameter[] ps = method.getParameters();
		if (ps == null || ps.length == 0) {
			return ar;
		}
		
		Map<String, Object> data = new HashMap<>(); 
		for (int i = 0; i < ps.length; i++) {
			data.put(String.valueOf(i), serializer.serializeAsString(args[i]));
		}
		
		ar.setData(JSON.toJSONString(data));
		
		return ar;
	}
}